﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Windows;
using System.IO;
using System.Data.SQLite;
using System.Windows.Media;
using System.Threading;
using System.Windows.Threading;

namespace KeyboardImageViewer
{
	public static class ImageListControl
	{
		private static List<int> imageList;
		private static int currentIndex = -1;
		private static int imageListVersion;
		private static bool playing;
		private static DispatcherTimer timer;

		private static bool clearCacheWhenLoadCompletes;

		static ImageListControl()
		{
			imageList = new List<int>();
		}

		public static int CurrentIndex
		{
			get { return currentIndex; }
		}

		public static int CurrentImageId
		{
			get
			{
				return imageList[currentIndex];
			}
		}

		public static ImageUserData CurrentImageData
		{
			get
			{
				return Database.GetImageUserData(ImageManager.Connection, CurrentImageId);
			}
		}

		public static bool Playing
		{
			get { return playing; }
		}

		public static List<int> ImageList
		{
			get { return imageList; }
		}

		public static int TotalImages
		{
			get { return imageList.Count; }
		}

		public static void MoveLeft()
		{
			MoveLeft(true);
		}

		private static void MoveLeft(bool userInitiated)
		{
			if (userInitiated)
			{
				StopSlideshowIfRunning();
			}

			if (ImageManager.ImageShowing && currentIndex > 0)
			{
				currentIndex--;

				ImageManager.MainForm.RemoveFromCache(ImageCache.Get(ImageCache.NumImagesToCacheAhead).UIImage);
				ImageCache.ShiftRight();

				// If we have an image cached, display it.
				if (ImageCache.Current.Image != null)
				{
					ImageManager.MainForm.SetPicture(ImageCache.Current);

					// Load more images if we need to.
					StartLoadingAdditionalImages();
				}
				else
				{
					// If we don't, start loading the image, if it's not loading already.
					ImageManager.MainForm.SetLoadingPicture();

					if (!ImageCache.Current.CallInProgress)
					{
						LoadImage(currentIndex);
					}
				}
			}
		}

		public static void MoveRight()
		{
			MoveRight(true);
		}

		private static void MoveRight(bool userInitiated)
		{
			if (userInitiated)
			{
				StopSlideshowIfRunning();
			}

			if (ImageManager.ImageShowing && currentIndex < imageList.Count - 1)
			{
				currentIndex++;

				ImageManager.MainForm.RemoveFromCache(ImageCache.Get(-ImageCache.NumImagesToCacheAhead).UIImage);
				ImageCache.ShiftLeft();

				// If we have an image cached, display it.
				if (ImageCache.Current.Image != null)
				{
					ImageManager.MainForm.SetPicture(ImageCache.Current);

					// Load more images if we need to.
					StartLoadingAdditionalImages();
				}
				else
				{
					// If we don't, start loading the image, if it's not loading already.
					ImageManager.MainForm.SetLoadingPicture();

					if (!ImageCache.Current.CallInProgress)
					{
						LoadImage(currentIndex);
					}
				}
			}
		}

		public static void MoveToStart()
		{
			SetCurrentIndex(0);
		}

		public static void MoveToEnd()
		{
			SetCurrentIndex(imageList.Count - 1);
		}

		public static void ToggleSlideshow()
		{
			if (!playing)
			{
				StartSlideshow();
			}
			else
			{
				StopSlideshow();
			}

			ImageManager.MainForm.UpdateInfoBar();
		}

		private static void AdvanceSlideshow(object sender, EventArgs e)
		{
			if (imageList.Count == 0)
			{
				return;
			}

			if (currentIndex == imageList.Count - 1)
			{
				StopSlideshow();
				MoveToStart();
				StartSlideshow();
			}
			else
			{
				MoveRight(false);
			}
		}

		public static bool StopSlideshowIfRunning()
		{
			if (playing)
			{
				StopSlideshow();
				ImageManager.MainForm.UpdateInfoBar();
				return true;
			}

			return false;
		}

		private static void StartSlideshow()
		{
			timer = new DispatcherTimer();
			timer.Interval = TimeSpan.FromSeconds(ImageManager.Config.SlideshowDelaySeconds);
			timer.Tick += AdvanceSlideshow;
			timer.Start();
			playing = true;
		}

		private static void StopSlideshow()
		{
			timer.Stop();
			timer = null;
			playing = false;
		}

		/// <summary>
		/// Runs the current image query, and if we have a new image list, make it current and change
		/// the view to the given index.
		/// </summary>
		/// <param name="startingIndex">The index to start at if we get a new image list.</param>
		public static void RunImageQuery(int startingIndex)
		{
			StopSlideshowIfRunning();
			List<int> newImageList = ImageQuery.GetImageList(ImageManager.Connection);

			if (newImageList != null)
			{
				imageList = newImageList;
				SetCurrentIndex(startingIndex);
			}
		}

		public static void RunImageQueryForced()
		{
			imageList = ImageQuery.GetImageList(ImageManager.Connection, true);
			SetCurrentIndex(0);
		}

		public static void RefreshImageCache()
		{
			SetCurrentIndex(currentIndex);
		}

		public static void DeleteCurrentImage()
		{
			StopSlideshowIfRunning();

			if (ImageCache.Current.Image != null)
			{
				if (MessageBox.Show("Really delete?", "Confirm delete", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
				{
					try
					{
						int imageId = CurrentImageId;
						string imageName = Database.GetImageName(ImageManager.Connection, imageId);

						// Delete file
						File.Delete(imageName);

						bool indexChanged = false;

						// Remove image from internal structures
						Database.RemoveImageData(imageId, ImageManager.Connection);

						imageList.RemoveAt(currentIndex);

						if (currentIndex == imageList.Count)
						{
							currentIndex--;
							indexChanged = true;
						}

						// Remove cached UI image.
						ImageManager.MainForm.RemoveFromCache(ImageCache.Current.UIImage);

						// Remove cached image data. If the index changed, we signal that this is the last item.
						ImageCache.DeleteCurrent(indexChanged);

						// Increment list version so in-progress calls are ignored when they complete.
						imageListVersion++;

						// Show our new cached image.
						ImageManager.MainForm.SetPicture(ImageCache.Current);

						// Start loading new images if we need to.
						if (imageList.Count > 0)
						{
							StartLoadingAdditionalImages();
						}
					}
					catch (IOException)
					{
						HandleDeleteException();
					}
					catch (UnauthorizedAccessException)
					{
						HandleDeleteException();
					}
				}
			}
		}

		/// <summary>
		/// Called when files have been deleted and we need to update our data structures.
		/// </summary>
		/// <param name="deletedImages">The images that have been deleted.</param>
		public static void RemoveDeletedImageData(List<int> deletedImages)
		{
			if (deletedImages.Count > 0)
			{
				foreach (int imageId in deletedImages)
				{
					int indexOfImage = imageList.IndexOf(imageId);

					if (indexOfImage >= 0)
					{
						imageList.RemoveAt(indexOfImage);

						if (indexOfImage < currentIndex)
						{
							currentIndex--;
						}
					}
				}

				Database.RemoveImageData(deletedImages, ImageManager.Connection);

				ImageCache.Current.Image = null;

				if (currentIndex == imageList.Count)
				{
					currentIndex--;
				}

				SetCurrentIndex(currentIndex);
			}
		}

		private static void SetCurrentIndex(int index)
		{
			StopSlideshowIfRunning();

			if (index >= 0 && index < imageList.Count)
			{
				// Set the current index.
				currentIndex = index;

				// Clear the UI cache when we're done doing the load.
				clearCacheWhenLoadCompletes = true;

				// Clear the image cache now.
				ImageCache.Clear();

				// Increment the image list version, so pending loads are thrown away.
				imageListVersion++;

				// Begin loading the image at the current index.
				LoadImage(currentIndex);
			}
			else
			{
				if (ImageQuery.HasFilter)
				{
					ImageManager.MainForm.ShowNoResults();
				}
				else
				{
					ImageManager.MainForm.ShowNoImages();
				}
			}
		}

		private static void LoadImage(int index)
		{
			BackgroundWorker imageLoader = new BackgroundWorker();
			double aspectRatio = ImageManager.MainForm.ImageAreaAspectRatio;
			int imageId = imageList[index];
			int startingImageListVersion = imageListVersion;

			ImageCache.Get(index - currentIndex).CallInProgress = true;

			imageLoader.DoWork += delegate(object sender, DoWorkEventArgs args)
			{
				System.Threading.Thread.CurrentThread.Priority = ThreadPriority.BelowNormal;

				SQLiteConnection workerConnection = new SQLiteConnection(Database.ConnectionString);
				workerConnection.Open();

				BackgroundWorker theWorker = sender as BackgroundWorker;

				args.Result = new ViewerImage(imageId, aspectRatio, workerConnection);
			};

			imageLoader.RunWorkerCompleted += delegate(object sender, RunWorkerCompletedEventArgs args)
			{
				// We've since thrown away the image list we started on, throw it away.
				if (startingImageListVersion != imageListVersion)
				{
					return;
				}

				if (clearCacheWhenLoadCompletes)
				{
					ImageManager.MainForm.ClearCache();

					clearCacheWhenLoadCompletes = false;
				}

				int indexOffset = index - currentIndex;

				// The image we've got isn't inside the cache window, throw it away.
				if (Math.Abs(indexOffset) > ImageCache.NumImagesToCacheAhead)
				{
					return;
				}

				// We already have an image in cache for this index, throw it away.
				if (ImageCache.Get(indexOffset).Image != null)
				{
					return;
				}

				ViewerImage finishedImage = args.Result as ViewerImage;

				// Populate the image cache with the built ViewerImage.
				ImageCache.Get(indexOffset).Image = finishedImage;
				ImageCache.Get(indexOffset).UIImage = ImageManager.MainForm.AddToCache(finishedImage);
				if (finishedImage.Rotation != 0)
				{
					ImageCache.Get(indexOffset).UIImage.LayoutTransform = new RotateTransform(finishedImage.Rotation);
				}

				// If we came back with the current image, display it.
				if (indexOffset == 0)
				{
					ImageManager.MainForm.SetPicture(ImageCache.Get(indexOffset));
				}

				// Start loading more images in the background if needed.
				StartLoadingAdditionalImages();
			};

			imageLoader.RunWorkerAsync();
		}

		private static void StartLoadingAdditionalImages()
		{
			if (ImageCache.Current.Image == null && !ImageCache.Current.CallInProgress)
			{
				LoadImage(currentIndex);
			}

			int leftBoundIndex = Math.Max(-ImageCache.NumImagesToCacheAhead, -currentIndex);
			int rightBoundIndex = Math.Min(ImageCache.NumImagesToCacheAhead, imageList.Count - currentIndex - 1);

			int furthestLoaded = -1;
			int furthestPossible = 0;

			if (ImageCache.Current.Image != null)
			{
				furthestLoaded = 0;
			}

			for (int i = 1; i <= ImageCache.NumImagesToCacheAhead; i++)
			{
				bool rightIndexInBounds = i <= rightBoundIndex;
				bool leftIndexInBounds = -i >= leftBoundIndex;

				if (rightIndexInBounds || leftIndexInBounds)
				{
					furthestPossible = i;
				}
				else
				{
					break;
				}

				bool rightImage = ImageCache.Get(i).Image != null;
				bool leftImage = ImageCache.Get(-i).Image != null;

				if ((rightImage || !rightIndexInBounds) && (leftImage || !leftIndexInBounds))
				{
					if (furthestLoaded == i - 1)
					{
						furthestLoaded++;
					}
				}
			}

			if (furthestLoaded < furthestPossible && furthestLoaded >= 0)
			{
				int indexOffsetToLoad = furthestLoaded + 1;

				if (-indexOffsetToLoad >= leftBoundIndex &&
					!ImageCache.Get(-indexOffsetToLoad).CallInProgress)
				{
					LoadImage(currentIndex - indexOffsetToLoad);
				}

				if (indexOffsetToLoad <= rightBoundIndex &&
					!ImageCache.Get(indexOffsetToLoad).CallInProgress)
				{
					LoadImage(currentIndex + indexOffsetToLoad);
				}
			}
		}

		private static void HandleDeleteException()
		{
			MessageBox.Show("Could not delete file.  Make sure that it is not write-protected or in use.", "Error with file delete");
		}
	}
}
